package common;

import java.math.BigInteger;
import java.security.SignatureException;
import java.util.Random;

public class AnonMath {
	/**
	 * create interpolation points (at first will be: 1,2,..,numOfShares)
	 * create interpolation values (shares)
	 */
	public static Point[] createInterpolationPoints(Integer[] coefficients) {
		Point[] interpolationPoints = new Point[Settings.numOfShares];		
		for (int i=0;i<Settings.numOfShares;i++){
			interpolationPoints[i]=new Point();
			interpolationPoints[i].setXValue(i + 1);
			interpolationPoints[i].setYValue(calculateValue(coefficients,interpolationPoints[i].getXValue()));
			//System.out.println(interpolationPoints[i]);
		}
		return interpolationPoints;
	}

	/**
	 * create random coefficients for polynomial of degree
	 * (threshold - 1)	
	 */
	public static Integer[] createPolynom(Integer secret) {		
		Random r = new Random();
		Integer[] coefficients = new Integer[Settings.threshold];
		coefficients[0] = secret;
		//System.out.println("Secret: " +secret);
		for (int i=1;i<Settings.threshold;i++){
 			coefficients[i] = r.nextInt(Settings.polynomialPrime.intValue()-1)+1;
			//System.out.println("coefficients "+ i+": " + coefficients[i]);
		}
		return coefficients;
	}

    public static String getCommitmentsAsString(BigInteger[] commitments, Integer[] coefficients) {
StringBuilder sb = new StringBuilder("Commitments:\n");
        for (int i=0; i<commitments.length; i++){
       //if (i%3==0)
         //      sb.append("\n");
        sb.append("C"+i+" = g^a"+i+" = "+Settings.generatorCommitmentPrime
                +"^"+coefficients[i]+" mod "+ Settings.commitmentPrime+" = "+commitments[i]+"\n");
                }
        return sb.toString();    }

    public static String getgetPolynomialAsString(Integer[] coefficients) {
       StringBuilder sb = new StringBuilder();
       sb.append(coefficients[0]);
       for (int i=1; i<coefficients.length; i++){
           if (i%5==0)
               sb.append("\n");
           sb.append(" + " + coefficients[i]+"x^"+i);
       }
      return sb.toString();
    }

    public static String getinterpolationPointsAString(Point[] interpolationPoints) {
        StringBuilder sb = new StringBuilder("Interpolation points (secret shares):");
        for (int i=0; i<interpolationPoints.length; i++){
       if (i%5==0)
               sb.append("\n");
        sb.append(interpolationPoints[i]+"  ");
        
        }
        return sb.toString();
    }

	/**
	 * Verifies a single share according to a given commitments
	 * @param share- the share to verify
	 * @param commitments- all of the user commitments
	 * @return true iff the share is verified by the commitments
	 */
	public static boolean verifyShare(Point share, BigInteger[] commitments) {
		debugPrint("Share Verifying:"); 
		BigInteger shareXValue = new BigInteger(share.getXValue().toString());
		BigInteger shareYValue = new BigInteger(share.getYValue().toString());
		debugPrint("x: "+ shareXValue);
		debugPrint("y: "+ shareYValue);
		BigInteger commitmentPrime = Settings.commitmentPrime;
		BigInteger expected = Settings.generatorCommitmentPrime.modPow(shareYValue, commitmentPrime);
		BigInteger multiplication = BigInteger.ONE;
		BigInteger exponent = BigInteger.ONE;
		for (Integer i=0; i<commitments.length; i++){
			exponent = shareXValue.modPow(new BigInteger(i.toString()), Settings.polynomialPrime);
			debugPrint("exponent: "+ exponent);
			multiplication = multiplication.multiply((commitments[i].modPow(exponent, commitmentPrime))).mod(commitmentPrime);
			debugPrint("mult: "+multiplication);
		}
		debugPrint("mult:     "+multiplication);
		debugPrint("expected: "+expected);
		return (expected.compareTo(multiplication)==0);
	}

	public static BigInteger[] generateCommitments(Integer[] coefficients) {          
		BigInteger[] commitments = new BigInteger[Settings.threshold];
		BigInteger g = Settings.generatorCommitmentPrime;
		BigInteger exponent;	
		for (int i=0; i<coefficients.length;i++) {
			exponent = new BigInteger(coefficients[i].toString());
			commitments[i]= g.modPow(exponent, Settings.commitmentPrime);
			//System.out.println("Commitment "+i+ ": ("+exponent+", " +commitments[i]+")");
		}
		return commitments;
	}

	/**
	 * Uses Lagrange interpolation in order to calculate the secret
	 * @param interpolationPoints- Points on the polynomial
	 * @return The polynomial value of X=0 (The secret)
	 */
	public static Integer interpolate(Point[] interpolationPoints) {
		BigInteger ans = new BigInteger("0");
		BigInteger xi,yi,xj;
		for (Integer i=0;i<interpolationPoints.length;i++){
			BigInteger numerator= new BigInteger("1");
			BigInteger denominator= new BigInteger("1");
			xi=new BigInteger(interpolationPoints[i].getXValue().toString());
			yi=new BigInteger(interpolationPoints[i].getYValue().toString());
			for (Integer j=0;j<interpolationPoints.length;j++){
				if (i!=j){
					xj=new BigInteger(interpolationPoints[j].getXValue().toString());
					numerator = numerator.multiply(BigInteger.ZERO.subtract(xj)).mod(Settings.polynomialPrime);
					denominator = denominator.multiply(xi.subtract(xj)).mod(Settings.polynomialPrime);
				}
			}
			ans = ans.add(
					(((numerator.multiply(denominator.modInverse(Settings.polynomialPrime)).mod(Settings.polynomialPrime))
							.multiply(yi)).mod(Settings.polynomialPrime))).mod(Settings.polynomialPrime);
		}
		return ans.intValue();
	}

	/**
	 * 
	 * @param coefficients- the polynomial coefficients
	 * @param xValue- the value of x
	 * @return The value of the polynomial represented by coefficients at the point xValue
	 */
	private static Integer calculateValue(Integer[] coefficients, Integer xValue) {
		BigInteger ans = new BigInteger("0");
		BigInteger tmpCoefficientAsBi;
		BigInteger tmpBiPow;
		BigInteger tmpBiMult;
		BigInteger xValueAsBi = new BigInteger(xValue.toString());
		for (Integer i=0; i < coefficients.length; i++) {
			tmpCoefficientAsBi = new BigInteger(coefficients[i].toString());
			tmpBiPow = xValueAsBi.modPow(new BigInteger(i.toString()), Settings.polynomialPrime);//.subtract(BigInteger.ONE));
			tmpBiMult = (tmpCoefficientAsBi.multiply(tmpBiPow)).mod(Settings.polynomialPrime);//.subtract(BigInteger.ONE));
			ans=ans.add(tmpBiMult).mod(Settings.polynomialPrime);
		}
		return ans.intValue();
	}

	public static void debugPrint(String s) {
		if (Settings.debugMode)
			System.out.println("Debug: " + s);
	}

	public static Boolean verifyCommitments(BigInteger[] commitmentsAs,
			byte[][] commitmentsSignatures, SignatureVerifier casv) {
		byte[] comSig;
		
		for (int i = 0; i < commitmentsSignatures.length; i++) {
			comSig = commitmentsSignatures[i];
			try {
				if (!casv.verify(commitmentsAs[i].toByteArray(), comSig))
					return false;
			} catch (SignatureException e) {
				System.out.println(e.getMessage());
				e.printStackTrace();
				return false;
			}
		}
		return true;
	}

	public static BigInteger[] convertStringToBigInteger(String[] commitments) {
		BigInteger[] biComs = new BigInteger[commitments.length];
		for (int i = 0; i < commitments.length; i++) {
			biComs[i] = new BigInteger(commitments[i]);
		}
		return biComs;
	}

	public static String[] convertBigIntegerToString(BigInteger[] commitments){
		String[] stringCommitmetns = new String[commitments.length];
		for (int i=0; i<commitments.length; i++){
			stringCommitmetns[i] = commitments[i].toString();
		}
		return stringCommitmetns;
	}

	public static byte[] intToByteArray(int value) {
		return new byte[] {
				(byte)(value >>> 24),
				(byte)(value >>> 16),
				(byte)(value >>> 8),
				(byte)value};
	}
	
	public static byte[][] getTableOfSignedCommitments(SignedData[] sc){
		byte[][] byteArr = new byte[sc.length][];
		
		for (int i = 0; i < sc.length; i++) {
			byteArr[i] = sc[i].getSignature();
		}
		return byteArr;
	}
}
